﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq;
using System.Threading;
using System.IO;
using System.Windows.Media.DirectShow;

namespace System.Windows.Media.DirectShow.Filters
{
    public abstract class BaseOutputPin : BasePin
    {

        private bool allocatorDecided;
        private BufferedOutputStream _buffer;

        public BaseOutputPin(IBaseFilter filter)
            : base(filter)
        {

        }

        public override PinDirection Direction
        {
            get { return PinDirection.Output; }
        }

        protected BufferedOutputStream Buffer
        {
            get { return _buffer; }
        }

        protected internal virtual IMediaSample GetDeliveryBuffer(TimeSpan startTime, TimeSpan endTime)
        {
            return this.Allocator.GetBuffer(startTime, endTime);
        }

        protected internal virtual void DeliverAsync(IMediaSample sample)
        {
            if (this.ConnectedPin != null)
            {
                // Supported only push mode
                IMemInputPin inputPin = this.ConnectedPin as IMemInputPin;
                if (inputPin == null)
                    throw new InvalidOperationException("The connected input pin does not support push data mode");

                // Send sample
                inputPin.Receive(sample);
            }
        }

        public virtual void ProcessSample(IMediaSample sample)
        {
            // Nothing to do
            if (this.ConnectedPin == null) return;

            BufferSample(sample);

            DeliverBuffer(false);
        }

        public virtual void ProcessStream(Stream stream, long offset, int length)
        {
            // Nothing to do
            if (this.ConnectedPin == null) return;

            this.Buffer.AppendStream(stream, offset, length);

            DeliverBuffer(false);
        }

        protected virtual void DeliverBuffer(bool flush)
        {
            lock (this)
            {
                if (this.Buffer == null) return;

                while ((flush && this.Buffer.Length > 0) || this.Buffer.HasEnoughtData)
                {
                    // Get sample for output pin negotiated with its input pin
                    IMediaSample newSample = this.Buffer.GetSample(TimeSpan.Zero, TimeSpan.Zero);
                    if (newSample == null) return;

                    newSample.AddRef();

                    try
                    {
                        this.DeliverAsync(newSample);
                    }
                    finally
                    {
                        newSample.Release();
                    }
                }
            }
        }

        protected virtual void BufferSample(IMediaSample sample)
        {
            lock (this)
            {
                this.Buffer.AppendSample(sample, 0);
            }
        }

        public virtual void Flush()
        {
            DeliverBuffer(true);
        }

        public virtual void DeliverEndOfStream()
        {
            if (this.ConnectedPin != null)
                this.ConnectedPin.EndOfStream();
        }

        protected virtual void QueryToConnectedPinNewMediaType(AMMediaType mediaType)
        {
            // Check media type compatibility
            if (this.ConnectedPin != null && !this.ConnectedPin.QueryAccept(mediaType))
                throw new ArgumentException("Input pin has refused sample media type", "mediaType");
        }

        protected override void ReceiveConnection(IPin connector, AMMediaType mediaType)
        {
            throw new InvalidOperationException("Cannot call this method. This is an output pin");
        }

        protected override void Connect(IPin receivePin, AMMediaType mediaType)
        {
            if (this.ConnectedPin != null)
                throw new InvalidOperationException("Pin already connected");

            this.ConnectedPin = receivePin;
            this.ConnectedMediaType = mediaType ?? (mediaType = this.MediaTypes.First());

            // Connect to the other input pin
            this.ConnectedPin.ReceiveConnection(this, mediaType);
            this.QueryToConnectedPinNewMediaType(mediaType);

            if (this.Allocator == null)
            {
                this.Allocator = this.DecideAllocator(receivePin as IMemInputPin);
                allocatorDecided = true;
            }
        }

        protected virtual IMemAllocator InitAllocator()
        {
            IMemAllocator a = new MemAllocator();

            return a;
        }

        protected virtual void DecideBufferSize(IMemAllocator allocator, AllocatorProperties ap)
        {
            allocator.Properties = ap;

            _buffer = new BufferedOutputStream(allocator);
        }

        protected virtual IMemAllocator DecideAllocator(IMemInputPin inputPin)
        {
            AllocatorProperties ap = null;
            IMemAllocator allocator = null;
            if (inputPin != null)
            {
                ap = inputPin.GetAllocatorRequirements();
                allocator = inputPin.GetAllocator();
            }

            if (allocator == null)
                allocator = InitAllocator();

            DecideBufferSize(allocator, ap);
            inputPin.NotifyAllocator(allocator, false);

            return allocator;
        }

        protected override void Disconnect()
        {
            if (this.Filter.State != FilterState.Stopped)
                throw new InvalidOperationException("Cannot disconnect while filter is running");

            this.ConnectedPin = null;
            this.ConnectedMediaType = null;
        }

        public override void Run(TimeSpan start)
        {
            if (allocatorDecided)
                this.Allocator.Commit();
            if (this.Buffer != null)
                this.Buffer.Reset();

            base.Run(start);
        }

        public override void Inactive()
        {
            base.Inactive();

            if (allocatorDecided)
                this.Allocator.Decommit();
        }
    }
}
